﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;
using System.Linq.Expressions;
using System.Threading;

namespace LevenBlog.Core.Reflection {
    public delegate object FastInvokeHandler(object target, params object[] paramters);

    public static class ReflectionExtensions {
        private static Dictionary<Type, Func<object>> _instanseCache = new Dictionary<Type, Func<object>>();
        private static ReaderWriterLockSlim _instanseLock = new ReaderWriterLockSlim();

        public static FastMethodInfo[] GetFastMethods(this Type type) {
            IList<FastMethodInfo> fastMethods = new List<FastMethodInfo>();
            MethodInfo[] methods = type.GetMethods();
            foreach (MethodInfo method in methods) {
                fastMethods.Add(new FastMethodInfo(method));
            }
            return fastMethods.ToArray();
        }

        public static FastPropertyInfo[] GetFastProperties(this Type type) {
            IList<FastPropertyInfo> fastProperties = new List<FastPropertyInfo>();
            PropertyInfo[] properties = type.GetProperties();
            foreach (PropertyInfo property in properties) {
                fastProperties.Add(new FastPropertyInfo(property));
            }
            return fastProperties.ToArray();
        }

        public static TResult LambdaCreate<TResult>()
            where TResult : class {
            //Func<object> fun;
            //Type type = typeof(TResult);
            //_instanseLock.EnterReadLock();
            //bool isHave = _instanseCache.TryGetValue(type, out fun);
            //_instanseLock.ExitReadLock();
            //if (!isHave) {
            //    NewExpression newExpression = Expression.New(
            //        typeof(TResult).GetConstructor(new Type[] { }));
            //    Expression<Func<object>> newLambda = Expression.Lambda<Func<object>>(
            //        newExpression, null);
            //    fun = newLambda.Compile();
            //    _instanseLock.EnterWriteLock();
            //    _instanseCache[type] = fun;
            //    _instanseLock.ExitWriteLock();
            //}
            //return (TResult)fun();
            NewExpression newExpression = Expression.New(
                    typeof(TResult).GetConstructor(new Type[] { }));
            Expression<Func<TResult>> newLambda = Expression.Lambda<Func<TResult>>(
                newExpression, null);
            return newLambda.Compile()();
        }

        public static object LambdaCreate(this Type type) {
            //Func<object> fun;
            //_instanseLock.EnterReadLock();
            //bool isHave = _instanseCache.TryGetValue(type, out fun);
            //_instanseLock.ExitReadLock();
            //if (!isHave) {
            //    NewExpression newExpression = Expression.New(type.GetConstructor(new Type[] { }));
            //    Expression<Func<object>> newLambda = Expression.Lambda<Func<object>>(newExpression, null);
            //    fun = newLambda.Compile();
            //    _instanseLock.EnterWriteLock();
            //    _instanseCache[type] = fun;
            //    _instanseLock.ExitWriteLock();
            //}
            //return fun();
            NewExpression newExpression = Expression.New(type.GetConstructor(new Type[] { }));
            Expression<Func<object>> newLambda = Expression.Lambda<Func<object>>(newExpression, null);
            return newLambda.Compile()();
        }

        public static FastInvokeHandler GetFastInvoker(this MethodInfo methodInfo) {
            DynamicMethod dynamicMethod = new DynamicMethod(string.Empty, typeof(object), new Type[] { typeof(object), typeof(object[]) }, methodInfo.DeclaringType.Module);
            ILGenerator il = dynamicMethod.GetILGenerator();
            ParameterInfo[] ps = methodInfo.GetParameters();
            Type[] paramTypes = new Type[ps.Length];
            for (int i = 0; i < paramTypes.Length; i++) {
                if (ps[i].ParameterType.IsByRef)
                    paramTypes[i] = ps[i].ParameterType.GetElementType();
                else
                    paramTypes[i] = ps[i].ParameterType;
            }
            LocalBuilder[] locals = new LocalBuilder[paramTypes.Length];

            for (int i = 0; i < paramTypes.Length; i++) {
                locals[i] = il.DeclareLocal(paramTypes[i], true);
            }
            for (int i = 0; i < paramTypes.Length; i++) {
                il.Emit(OpCodes.Ldarg_1);
                EmitFastInt(il, i);
                il.Emit(OpCodes.Ldelem_Ref);
                EmitCastToReference(il, paramTypes[i]);
                il.Emit(OpCodes.Stloc, locals[i]);
            }
            if (!methodInfo.IsStatic) {
                il.Emit(OpCodes.Ldarg_0);
            }
            for (int i = 0; i < paramTypes.Length; i++) {
                if (ps[i].ParameterType.IsByRef)
                    il.Emit(OpCodes.Ldloca_S, locals[i]);
                else
                    il.Emit(OpCodes.Ldloc, locals[i]);
            }
            if (methodInfo.IsStatic)
                il.EmitCall(OpCodes.Call, methodInfo, null);
            else
                il.EmitCall(OpCodes.Callvirt, methodInfo, null);
            if (methodInfo.ReturnType == typeof(void))
                il.Emit(OpCodes.Ldnull);
            else
                EmitBoxIfNeeded(il, methodInfo.ReturnType);

            for (int i = 0; i < paramTypes.Length; i++) {
                if (ps[i].ParameterType.IsByRef) {
                    il.Emit(OpCodes.Ldarg_1);
                    EmitFastInt(il, i);
                    il.Emit(OpCodes.Ldloc, locals[i]);
                    if (locals[i].LocalType.IsValueType)
                        il.Emit(OpCodes.Box, locals[i].LocalType);
                    il.Emit(OpCodes.Stelem_Ref);
                }
            }

            il.Emit(OpCodes.Ret);
            FastInvokeHandler invoder = (FastInvokeHandler)dynamicMethod.CreateDelegate(typeof(FastInvokeHandler));
            return invoder;
        }

        private static void EmitCastToReference(ILGenerator il, System.Type type) {
            if (type.IsValueType) {
                il.Emit(OpCodes.Unbox_Any, type);
            } else {
                il.Emit(OpCodes.Castclass, type);
            }
        }

        private static void EmitBoxIfNeeded(ILGenerator il, System.Type type) {
            if (type.IsValueType) {
                il.Emit(OpCodes.Box, type);
            }
        }

        private static void EmitFastInt(ILGenerator il, int value) {
            switch (value) {
                case -1:
                    il.Emit(OpCodes.Ldc_I4_M1);
                    return;
                case 0:
                    il.Emit(OpCodes.Ldc_I4_0);
                    return;
                case 1:
                    il.Emit(OpCodes.Ldc_I4_1);
                    return;
                case 2:
                    il.Emit(OpCodes.Ldc_I4_2);
                    return;
                case 3:
                    il.Emit(OpCodes.Ldc_I4_3);
                    return;
                case 4:
                    il.Emit(OpCodes.Ldc_I4_4);
                    return;
                case 5:
                    il.Emit(OpCodes.Ldc_I4_5);
                    return;
                case 6:
                    il.Emit(OpCodes.Ldc_I4_6);
                    return;
                case 7:
                    il.Emit(OpCodes.Ldc_I4_7);
                    return;
                case 8:
                    il.Emit(OpCodes.Ldc_I4_8);
                    return;
            }

            if (value > -129 && value < 128) {
                il.Emit(OpCodes.Ldc_I4_S, (SByte)value);
            } else {
                il.Emit(OpCodes.Ldc_I4, value);
            }
        }
    }
}
